// Copyright DustedPixels.com 2008. All rights reserved.

package com.dustedpixels.jasmin.ui;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.LinkedList;

import com.dustedpixels.jasmin.video.VideoOutput;

/**
 * @author micapolos@gmail.com (Michal Pociecha-Los)
 */
public final class VideoOutputCanvas extends Canvas implements VideoOutput {
  private static final long serialVersionUID = -8208106895572675457L;
  private static final int BUFFER_SIZE = 2;
  
  private int width = 0;
  private int height = 0;

  private LinkedList<BufferedImage> queuedImages;
  private LinkedList<BufferedImage> spareImages;
  
  private BufferedImage producedImage;
  
  private final Object mutex = new Object();
  private boolean muted = false;
  
  public VideoOutputCanvas() {
    this.width = 0;
    this.height = 0;
    
    // Initialize images
    queuedImages = new LinkedList<BufferedImage>();
    spareImages = new LinkedList<BufferedImage>();
    
    // Prepare default image which will be drawn.
    // TODO(micapolos): Read it from file.
    BufferedImage initialImage = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB);
    int[] frameData = ((DataBufferInt) initialImage.getRaster().getDataBuffer()).getData();
    for (int x = 0; x < 400; x++) {
      for (int y = 0; y < 300; y++) {
        frameData[x + y * 400] = (x < 200 ^ y < 150) ? 0x00FF0000 : 0x000000FF;
      }
    }
    queuedImages.add(initialImage);
  }
  
  public void setSize(int width, int height) {
    synchronized (mutex) {
      this.width = width;
      this.height = height;
    }
  }
  
  public boolean needsFrame() {
    synchronized (mutex) {
      return queuedImages.size() != BUFFER_SIZE && !muted;
    }
  }

  public int[] beginFrame() {
    //System.out.println("Begin frame");
    synchronized (mutex) {
      if (producedImage != null) {
        throw new IllegalStateException();
      }
      
      if (queuedImages.size() == BUFFER_SIZE) {
        producedImage = queuedImages.removeLast();
      } else if (spareImages.isEmpty()) {
        producedImage = newBufferedImage();
      } else {
        producedImage = spareImages.removeLast();
        if (producedImage.getWidth() != width || producedImage.getHeight() != height) {
          producedImage = newBufferedImage();
        }
      }
            
      return getFrameBuffer(producedImage);
    }
  }
  
  public void endFrame() {
    //System.out.println("End frame");
    synchronized (mutex) {
      if (producedImage == null) {
        throw new IllegalStateException();
      }
      
      queuedImages.addLast(producedImage);
      producedImage = null;
    }
    
    repaint();
  }
  
  @Override
  public void paint(Graphics graphics) {
    BufferedImage toPaint = null;
    
    synchronized (mutex) {
      if (queuedImages.size() == 1) {
        toPaint = queuedImages.getFirst();
      } else {
        toPaint = queuedImages.removeFirst();
      }
    }

    graphics.drawImage(toPaint, 0, 0, getWidth(), getHeight(), null);
    
    synchronized (mutex) {
      spareImages.addLast(toPaint);
    }
  }
  
  @Override
  public Dimension getPreferredSize() {
    return new Dimension(48+256+48, 48+192+48);
  }
  
  public void setMuted(boolean muted) {
    synchronized (mutex) {
      this.muted  = muted;
    }
  }
  
  private BufferedImage newBufferedImage() {
    //System.out.println("new BufferedImage(" + width + ", " + height + ")");
    return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
  }
  
  private int[] getFrameBuffer(BufferedImage image) {
    return ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
  }
}
